package by.demchenko.sample.dataaccess.impl;

import by.demchenko.sample.dataaccess.base.ResultDao;
import by.demchenko.sample.datamodel.*;
import org.hibernate.jpa.criteria.OrderImpl;
import org.springframework.stereotype.Repository;

import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import javax.persistence.metamodel.SingularAttribute;
import java.security.InvalidParameterException;
import java.util.List;

@Repository
public class ResultDaoImpl extends AbstractDaoImpl<Integer, Result> implements ResultDao {

    protected ResultDaoImpl() {
        super(Result.class);
    }

    @Override
    public Result getResult(Person student, Course course) {
        CriteriaBuilder cBuilder = getEm().getCriteriaBuilder();

        CriteriaQuery<Result> criteria = cBuilder.createQuery(Result.class);
        Root<Result> root = criteria.from(Result.class);

        criteria.select(root);

        criteria.where(
                cBuilder.and(
                        cBuilder.equal(root.get(Result_.student), student),
                        cBuilder.equal(root.get(Result_.schedule).get(Schedule_.course), course)
                )
        );

        TypedQuery<Result> query = getEm().createQuery(criteria);
        List<Result> results = query.getResultList();

        if (results.size() == 1) {
            return results.get(0);
        } else if (results.size() > 1) {
            throw new InvalidParameterException("Find more than one grade for the student!");
        }

        return null;
    }

    @Override
    public List<Result> getAllResults() {
        CriteriaBuilder cBuilder = getEm().getCriteriaBuilder();

        CriteriaQuery<Result> criteria = cBuilder.createQuery(Result.class);
        Root<Result> root = criteria.from(Result.class);

        criteria.select(root);

        TypedQuery<Result> query = getEm().createQuery(criteria);
        return query.getResultList();
    }

    @Override
    public List<Result> getResults(SingularAttribute<Result, ?> attr, boolean ascending, int startRecord, int pageSize) {
        CriteriaBuilder cBuilder = getEm().getCriteriaBuilder();

        CriteriaQuery<Result> criteria = cBuilder.createQuery(Result.class);
        Root<Result> root = criteria.from(Result.class);

        criteria.select(root);

        //Q Different fetches and orders
        root.fetch("student").fetch("faculty");
        criteria.distinct(true);

        criteria.orderBy(new OrderImpl(root.get(attr), ascending));

        TypedQuery<Result> query = getEm().createQuery(criteria);
        query.setFirstResult(startRecord);
        query.setMaxResults(pageSize);

        return query.getResultList();

    }

    @Override
    public boolean canAssignTheGrade(Person student, Course course) {

        CriteriaBuilder cBuilder = getEm().getCriteriaBuilder();

        CriteriaQuery<Person> criteria = cBuilder.createQuery(Person.class);
        Root<Person> root = criteria.from(Person.class);
        Join courses = root.join("schedules").join("course");

        criteria.select(root);
        Predicate p1 = cBuilder.equal(root.get(Person_.id), student.getId());
        Predicate p2 = cBuilder.equal(courses.get(Course_.id), course.getId());
        criteria.where(cBuilder.and(p1, p2));

        TypedQuery<Person> query = getEm().createQuery(criteria);
        List<Person> results = query.getResultList();

        if (results.size() == 1)
            return true;
        return false;

        //In PJQL
        //        TypedQuery<Person> query =
//                getEm().createQuery(
//                        "SELECT p FROM Person p" +
//                                " JOIN FETCH p.schedules sc" +
//                                " JOIN FETCH sc.course c" +
//                                " WHERE p = :student AND c = :course"
//                        , Person.class);
//        query.setParameter("course", course);
//        query.setParameter("student", student);
//
//        List<Person> results = query.getResultList();
//
//        if (results.size() == 1) {
//            return true;
//        } else if (results.size() > 1) {
//            throw new IllegalStateException("Find more than one student registration!");
//        }
//
//        return false;

    }

    @Override
    public List<Result> getResultsByStudent(Person student) {
        CriteriaBuilder cBuilder = getEm().getCriteriaBuilder();

        CriteriaQuery<Result> criteria = cBuilder.createQuery(Result.class);
        Root<Result> root = criteria.from(Result.class);

        criteria.select(root);
        root.fetch("schedule");
        root.fetch("schedule").fetch("course").fetch("section");
        root.fetch("schedule").fetch("course").fetch("teacher");
        criteria.distinct(true);

        criteria.where(cBuilder.equal(root.get(Result_.student), student));

        TypedQuery<Result> query = getEm().createQuery(criteria);
        return query.getResultList();
    }

    @Override
    public int getCount() {
        CriteriaBuilder cBuilder = getEm().getCriteriaBuilder();

        CriteriaQuery<Long> criteria = cBuilder.createQuery(Long.class);
        Root<Result> root = criteria.from(Result.class);

        criteria.select(cBuilder.count(root));

        TypedQuery<Long> query = getEm().createQuery(criteria);
        return query.getSingleResult().intValue();
    }

    @Override
    public List<Result> getResultsByCourse(Schedule schedule) {
        CriteriaBuilder cBuilder = getEm().getCriteriaBuilder();

        CriteriaQuery<Result> criteria = cBuilder.createQuery(Result.class);
        Root<Result> root = criteria.from(Result.class);

        criteria.select(root);
        root.fetch("schedule");
        root.fetch("student");
        root.fetch("student").fetch("faculty");
        criteria.distinct(true);

        criteria.where(cBuilder.equal(root.get(Result_.schedule), schedule));

        TypedQuery<Result> query = getEm().createQuery(criteria);
        return query.getResultList();
    }

    @Override
    public void createTable(Schedule scheduleRecord) {
        for (Person student: scheduleRecord.getStudents()) {
            Result result = new Result();
            result.setSchedule(scheduleRecord);
            result.setStudent(student);
            insert(result);
        }
    }

}